%%%-------------------------------------------------------------------
%%% @author mirahs(2892727493@qq.com)
%%% @doc
%%%     定时(分钟)管理器
%%% @end
%%%-------------------------------------------------------------------
-module(crontab_mgr).

-behaviour(gen_server).

-export([reload/0]).

-export([start_link/0]).

-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).

-include("common.hrl").
-include("env.hrl").

-record(state, {module}).


%%%===================================================================
%%% API functions
%%%===================================================================

%% 重新加载(使用 gm 修改时间后调用)
reload() ->
    ?MODULE ! reload.


%%%===================================================================
%%% Spawning and gen_server implementation
%%%===================================================================

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).


init([]) ->
    ticket(),
    Module = env_mgr:get(?ENV_CRONTAB_HANDLER),
    {ok, #state{module = Module}}.

handle_call(_Request, _From, State) ->
    {reply, ok, State}.

handle_cast(_Request, State) ->
    {noreply, State}.

handle_info(run, State = #state{module = Module}) ->
    ticket(),
    task_run(Module),
    {noreply, State};

handle_info(reload, State) ->
    case erlang:get(timer_run) of
        undefined -> ok;
        TimerRef -> erlang:cancel_timer(TimerRef)
    end,
    ticket(),
    {noreply, State};

handle_info(_Info, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.


%%%===================================================================
%%% Internal functions
%%%===================================================================

ticket() ->
    {MegaSecs, Secs, MicroSecs} = util_time:get_now(),

    NowMs = (MegaSecs * 1000000 + Secs) * 1000 + MicroSecs div 1000,
    NextMinuteMs = ((NowMs div 60000) + 1) * 60000,

    Timer = erlang:send_after(NextMinuteMs - NowMs, erlang:self(), run),
    erlang:put(timer_run, Timer).


task_run(Module) ->
    LocalTime = {Date, _Time} = util_time:datetime(),
    Week = calendar:day_of_the_week(Date),
    task_run(Module:tasks(), {LocalTime, Week, Module:day_open(), Module:day_merge()}).

task_run([{Min, Hour, Day, Month, Week, {Mod, Fun, Args}} | Tasks], Arg = {{{_Y,M,D}, {H,I,_S}}, W, _DayOpen, _DayMerge}) ->
    case check_time([{Min, I}, {Hour, H}, {Day, D}, {Month, M}, {Week, W}]) of
        true ->
            erlang:spawn(Mod, Fun, Args),
            task_run(Tasks, Arg);
        false -> task_run(Tasks, Arg)
    end;
task_run([{Min, Hour, Day, Month, Week, OpenLimit, Opens, MergeLimit, Merges, {Mod, Fun, Args}} | Tasks], Arg = {{{_Y,M,D}, {H,I,_S}}, W, DayOpen, DayMerge}) ->
    case check_time([{Min, I}, {Hour, H}, {Day, D}, {Month, M}, {Week, W}, {limit, OpenLimit, DayOpen}, {Opens, DayOpen}, {limit, MergeLimit, DayMerge}, {Merges, DayMerge}]) of
        true ->
            erlang:spawn(Mod, Fun, Args),
            task_run(Tasks, Arg);
        false -> task_run(Tasks, Arg)
    end;
task_run([], _Arg) ->
    ok.


check_time([]) -> true;
check_time([H | T]) ->
    case check_time2(H) of
        true -> check_time(T);
        false -> false
    end.

check_time2({all, _Value}) -> true;
check_time2({[], _Value}) -> true;
check_time2({TaskTime, Value}) when erlang:is_integer(TaskTime) -> TaskTime =:= Value;
check_time2({TaskTimes, Value}) -> lists:member(Value, TaskTimes);
check_time2({limit, all, _Value}) -> true;
check_time2({limit, [], _Value}) -> true;
check_time2({limit, Limit, Value}) -> Value >= Limit.
